package com.gitee.feizns.quickstart.ali.pay;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.AlipayRequest;
import com.alipay.api.AlipayResponse;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayFundTransUniTransferModel;
import com.alipay.api.request.AlipayFundTransUniTransferRequest;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayFundTransUniTransferResponse;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.alipay.api.response.AlipayTradePagePayResponse;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import lombok.Getter;
import lombok.SneakyThrows;

import java.io.IOException;
import java.util.function.Consumer;

/**
 * 支付宝支付
 * <p>
 *     配置{@link AlipayConfig}即可开用.<br/>
 * </p>
 * <a href="https://opendocs.alipay.com/open/200">支付宝文档</a>
 * <a href="https://opendocs.alipay.com/open/00a0ut">支付宝服务端文档</a>
 * @author feizns
 * @since 2022/11/18
 */
public class AlipayUtils {

    /**
     * 支付宝api正式网关（新版api）
     */
    public static final String PRODUCT_SERVER_URL = "https://openapi.alipay.com/gateway.do";

    /**
     * 支付宝api测试沙盒环境网关
     */
    public static final String DEV_SERVER_URL = "https://openapi.alipaydev.com/gateway.do";

    /**
     * 支付配置类
     * {@link AlipayConfig}
     */
    @Getter
    private final AlipayConfig config;

    /**
     * 支付请求客户端
     * {@link AlipayClient}
     */
    private final AlipayClient client;

    /**
     * 通过配置{@link AlipayConfig}来配置请求参数
     * @param config {@link AlipayConfig}
     */
    @SneakyThrows
    public AlipayUtils(AlipayConfig config) {
        this.config = config;
        this.client = new DefaultAlipayClient(config);
    }

    /**
     * App支付生成订单字符串
     * <pre>{@code
     *  //将支付宝生成的支付页面表单写入响应流
     *  HttpServletResponse resp = WebUtils.getResp();
     *  resp.setContentType("text/html;charset=" + alipayConfig.getCharset());
     *  resp.getWriter().write(appPay('outTradeNo', 0.2, 'subject', 'productCode').getBody());
     *  resp.getWriter().flush();
     * }</pre>
     * @link <a href="https://opendocs.alipay.com/open/204/01dcc0">快速接入支付宝App支付</a>
     * @param outTradeNo 订单编号
     * @param subject 商品名称
     * @param totalAmount 价格
     * @param productCode 产品码
     * @return {@link AlipayTradeAppPayResponse}
     */
    public AlipayTradeAppPayResponse appPay(String outTradeNo, double totalAmount, String subject, String productCode) throws AlipayApiException {
        return appPay(req -> req.setBizContent(createBizContent(outTradeNo, totalAmount, subject, productCode).toJSONString()));
    }

    /**
     * App支付生成订单字符串
     * <pre>{@code
     *  //将支付宝生成的支付页面表单写入响应流
     *  HttpServletResponse resp = WebUtils.getResp();
     *  resp.setContentType("text/html;charset=" + alipayConfig.getCharset());
     *  resp.getWriter().write(appPay(req -> {}).getBody());
     *  resp.getWriter().flush();
     * }</pre>
     * <a href="https://opendocs.alipay.com/open/204/01dcc0">快速接入支付宝App支付</a>
     * @param requestConsumer 配置请求
     * @return {@link AlipayTradeAppPayResponse}
     */
    public AlipayTradeAppPayResponse appPay(Consumer<AlipayTradeAppPayRequest> requestConsumer) throws AlipayApiException {
        return client.sdkExecute(createRequest(AlipayTradeAppPayRequest.class, requestConsumer));
    }

    /**
     * 电脑网站支付生成表单
     * <pre>{@code
     *  //将支付宝生成的支付页面表单写入响应流
     *  HttpServletResponse resp = WebUtils.getResp();
     *  resp.setContentType("text/html;charset=" + alipayConfig.getCharset());
     *  resp.getWriter().write(pagePay('outTradeNo', 0.2, 'subject', 'productCode').getBody());
     *  resp.getWriter().flush();
     * }</pre>
     * @link <a href="https://opendocs.alipay.com/open/270/105899">支付宝电脑网站支付快速接入</a>
     * @param outTradeNo 订单编号
     * @param subject 商品名称
     * @param totalAmount 价格
     * @param productCode 产品码
     * @return {@link AlipayTradePagePayResponse}
     * @throws AlipayApiException 支付异常
     */
    public AlipayTradePagePayResponse pagePay(String outTradeNo, double totalAmount, String subject, String productCode) throws AlipayApiException, IOException {
        return pagePay(req -> req.setBizContent(createBizContent(outTradeNo, totalAmount, subject, productCode).toJSONString()));
    }

    /**
     * 电脑网站支付生成表单
     * <pre>{@code
     *  //将支付宝生成的支付页面表单写入响应流
     *  HttpServletResponse resp = WebUtils.getResp();
     *  resp.setContentType("text/html;charset=" + alipayConfig.getCharset());
     *  resp.getWriter().write(pagePay(req -> {}).getBody());
     *  resp.getWriter().flush();
     * }</pre>
     * @link <a href="https://opendocs.alipay.com/open/270/105899">支付宝电脑网站支付快速接入</a>
     * @param requestConsumer 配置请求
     * @return {@link AlipayTradePagePayResponse}
     * @throws AlipayApiException 支付异常
     */
    public AlipayTradePagePayResponse pagePay(Consumer<AlipayTradePagePayRequest> requestConsumer) throws AlipayApiException {
        return client.pageExecute(createRequest(AlipayTradePagePayRequest.class, requestConsumer));
    }

    /**
     * 手机网站支付生成表单
     * <pre>{@code
     *  //将支付宝生成的支付页面表单写入响应流
     *  HttpServletResponse resp = WebUtils.getResp();
     *  resp.setContentType("text/html;charset=" + alipayConfig.getCharset());
     *  resp.getWriter().write(wapPay('订单编号', 0.2, 'subject', 'productCode').getBody());
     *  resp.getWriter().flush();
     * }</pre>
     * @param outTradeNo 订单编号
     * @param totalAmount 价格
     * @param subject 商品名称
     * @param productCode 产品码
     * @return {@link AlipayTradeWapPayResponse}
     * @throws AlipayApiException 支付异常
     */
    public AlipayTradeWapPayResponse wapPay(String outTradeNo, double totalAmount, String subject, String productCode) throws AlipayApiException {
        return wapPay(req -> req.setBizContent(createBizContent(outTradeNo, totalAmount, subject, productCode).toJSONString()));
    }

    /**
     * 手机网站支付生成表单
     * <pre>{@code
     *  //将支付宝生成的支付页面表单写入响应流
     *  HttpServletResponse resp = WebUtils.getResp();
     *  resp.setContentType("text/html;charset=" + alipayConfig.getCharset());
     *  resp.getWriter().write(wapPay(req -> {}).getBody());
     *  resp.getWriter().flush();
     * }</pre>
     * @link <a href="https://opendocs.alipay.com/open/203/105285">支付宝手机网站支付快速接入</a>
     * @param requestConsumer 请求配置
     * @return {@link AlipayTradeWapPayResponse}
     * @throws AlipayApiException 支付异常
     */
    public AlipayTradeWapPayResponse wapPay(Consumer<AlipayTradeWapPayRequest> requestConsumer) throws AlipayApiException {
        return client.pageExecute(createRequest(AlipayTradeWapPayRequest.class, requestConsumer));
    }

    /**
     * 单笔转账接口
     * @link <a href="https://opendocs.alipay.com/open/02byuo?ref=api&scene=ca56bca529e64125a2786703c6192d41#%E8%AF%B7%E6%B1%82%E7%A4%BA%E4%BE%8B">单笔转账接口</a>
     * @param modelConsumer {@link AlipayFundTransUniTransferModel}
     * @return {@link AlipayFundTransUniTransferResponse}
     */
    public AlipayFundTransUniTransferResponse transfer(Consumer<AlipayFundTransUniTransferModel> modelConsumer) {
        AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel();
        modelConsumer.accept(model);
        AlipayFundTransUniTransferRequest transferRequest = createRequest(AlipayFundTransUniTransferRequest.class, req -> req.setBizModel(model));
        try {
            return client.certificateExecute(transferRequest);
        } catch (AlipayApiException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 创建一个{@link AlipayRequest}并配置参数
     * @param reqType {@link AlipayRequest}的具体类型
     * @param consumer 对{@link AlipayRequest}进行配置
     * @return {@link AlipayRequest}
     * @param <T> 对应{@link AlipayRequest}的具体类型
     */
    public <T extends AlipayRequest<? extends AlipayResponse>> T createRequest(Class<T> reqType, Consumer<T> consumer) {
        try {
            T request = reqType.newInstance();
            request.setReturnUrl(config.getReturnUrl());
            request.setNotifyUrl(config.getReturnUrl());
            consumer.accept(request);
            return request;
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 创建一个必传参数的业务对象
     * @param outTradeNo 订单号，（请保证每次请求支付宝api唯一）
     * @param totalAmount 商品价格（注意精度）
     * @param productCode 产品码
     * @param subject 商品名称
     * @param body 商品详情
     * @return 返回一个数据的JSONObject
     */
    public JSONObject createBizContent(String outTradeNo, double totalAmount, String subject, String productCode, String body) {
        JSONObject content = createBizContent(outTradeNo, totalAmount, subject, productCode);
        content.put("body", body);
        return content;
    }

    /**
     * 创建一个必传参数的业务对象
     * @param outTradeNo 订单号，（请保证每次请求支付宝api唯一）
     * @param totalAmount 商品价格（注意精度）
     * @param productCode 产品码
     * @param subject 商品名称
     * @return 返回一个数据的JSONObject
     */
    public JSONObject createBizContent(String outTradeNo, double totalAmount, String subject, String productCode) {
        JSONObject content = new JSONObject();
        content.put("out_trade_no", outTradeNo);
        content.put("total_amount", totalAmount);
        content.put("product_code", productCode);
        content.put("subject", subject);
        return content;
    }

}
